package com.backend.securitiessystem.crawler.service.impl;

import com.backend.securitiessystem.crawler.po.*;
import com.backend.securitiessystem.crawler.properties.CrawlerProperties;
import com.backend.securitiessystem.crawler.service.CrawlerProvider;
import com.backend.securitiessystem.enums.AppHttpCodeEnum;
import com.backend.securitiessystem.exception.BaseException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 爬虫数据提供者 impl
 *
 * @author qksuki
 * @date 2024-05-20 04:25:10
 */
@Component
@Slf4j
public class CrawlerProviderImpl implements CrawlerProvider {
    @Resource
    CrawlerProperties crawlerProperties;

    @Override
    public List<CrawledChinaIndex> getCrawledChinaIndexList() {
        Document document = getTargetDocument(crawlerProperties.getAsianChinaStockMarketsUrl());

        List<CrawledChinaIndex> crawledChinaIndexList = new ArrayList<>();
        Elements elements = document.select("tr.odd, tr.even");
        elements.forEach(element -> {
            // 股市名称
            String name = element.select("td.mywidth a").text();

            if (!crawlerProperties.getAsianChinaStockMarkets().contains(name)) {
                return;
            }

            // 最新价格
            String latestPrice = element.select("td.mywidth2").text().split(" ")[0];

            if (StringUtils.isBlank(name) || StringUtils.isBlank(latestPrice)) {
                return;
            }

            CrawledChinaIndex crawledChinaIndex = new CrawledChinaIndex(name, Double.parseDouble(latestPrice));
            crawledChinaIndexList.add(crawledChinaIndex);
        });
        return crawledChinaIndexList;
    }

    @Override
    public List<CrawledNewShare> getCrawledNewShareList() {
        Document document = getTargetDocument(crawlerProperties.getNewShareUrl());

        List<CrawledNewShare> crawledNewShareList = new ArrayList<>();
        Elements elements = document.select("tr");
        elements.forEach(element -> {
            // 通过 tr 去检索对应 td
            Elements row = element.select("td");
            if (row.isEmpty() || row.size() < 5) {
                return;
            }

            String name = row.get(2).text();
            String dateStr = row.get(4).text();

            if ("证券简称".equals(name) || dateStr.isEmpty()) {
                return;
            }

            CrawledNewShare crawledNewShare = null;
            try {
                crawledNewShare = new CrawledNewShare(name, new SimpleDateFormat("yyyy-MM-dd").parse(dateStr));
            } catch (ParseException e) {
                log.error("日期转换异常: {}", e.getMessage());
            }

            crawledNewShareList.add(crawledNewShare);
        });

        return crawledNewShareList;
    }

    @Override
    public List<CrawledSecuritiesNews> getCrawledSecuritiesNewsList() {
        Document targetDocument = getTargetDocument(crawlerProperties.getSecuritiesNewsUrl());

        List<CrawledSecuritiesNews> crawledSecuritiesNewsList = new ArrayList<>();
        Elements elements = targetDocument.select(".news_item");
        elements.forEach(element -> {
            // 时间
            String timeStr = element.select(".news_time").text();
            // 普通信息
            String normalInfo = element.select(".news_detail_text ").text();
            // 提示信息
            String promptInfo = element.select(".news_detail_text.font_3").text();

            // 日期修正，添加当前日期 + 爬出的时间 (HH:mm)
            String currentDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
            String dateTimeStr = currentDate + " " + timeStr;
            Date combinedDateTime;

            try {
                combinedDateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(dateTimeStr);
            } catch (ParseException e) {
                throw new BaseException(AppHttpCodeEnum.DATE_PARSING_FAILURE);
            }

            CrawledSecuritiesNews crawledSecuritiesNews;
            if (!promptInfo.isEmpty()) {
                crawledSecuritiesNews = new CrawledSecuritiesNews(promptInfo, 2, combinedDateTime);
            } else {
                crawledSecuritiesNews = new CrawledSecuritiesNews(normalInfo, 1, combinedDateTime);
            }
            crawledSecuritiesNewsList.add(crawledSecuritiesNews);
        });

        return crawledSecuritiesNewsList;
    }

    @Override
    public List<CrawledCapitalFlow> getCrawledCapitalFlowList() {
        Document targetDocument = getTargetDocument(crawlerProperties.getCapitalFlowUrl());

        List<CrawledCapitalFlow> crawledCapitalFlowList = new ArrayList<>();
        Elements elements = targetDocument.select("table.dataTable.no-footer tbody tr");

        // 这个吊毛表格上下 td 数据不对齐的，需要手动修正
        boolean isTableButton = false;
        int[] tds = {0, 4, 5, 6};

        for (Element element : elements) {
            Elements row = element.select("td");
            if (row.isEmpty() || row.size() < 8) {
                continue;
            }

            if (isTableButton) {
                tds = Arrays.stream(tds).map(e -> e - 1).toArray();
            } else {
                tds = Arrays.stream(tds).map(e -> e + 1).toArray();
            }

            isTableButton = !isTableButton;

            String flowName = row.get(tds[0]).text();
            String theFlow = row.get(tds[1]).text();
            String theBalance = row.get(tds[2]).text();
            String theLimit = row.get(tds[3]).text();

            String flow = null;
            String balance = null;

            // 修正数据，删除 “亿元” 后缀
            if (!theFlow.isEmpty() && !"-".equals(theFlow)) {
                flow = theFlow.substring(0, theFlow.length() - 2);
            }

            // 当日资金余额会显示 “额度充足” 而不是具体的数值
            if (!"额度充足".equals(theBalance)) {
                balance = theBalance.substring(0, theBalance.length() - 2);
            }

            String limit = theLimit.substring(0, theLimit.length() - 2);

            CrawledCapitalFlow crawledCapitalFlow = new CrawledCapitalFlow(flowName, (double) Integer.MIN_VALUE, (double) Integer.MIN_VALUE, Double.parseDouble(limit));

            // flow balance 有可能是 “-”
            if (flow != null && !"-".equals(flow)) {
                crawledCapitalFlow.setFlow(Double.parseDouble(flow));
            }

            // balance 有可能是额度充足
            if (balance != null && !"额度充足".equals(balance)) {
                crawledCapitalFlow.setBalance(Double.parseDouble(balance));
            }

            crawledCapitalFlowList.add(crawledCapitalFlow);
        }
        return crawledCapitalFlowList;
    }

    @Override
    public List<CrawledRiseFall> getCrawledRiseFallList() {
        Document document = getTargetDocument(crawlerProperties.getRiseFallUrl());

        List<CrawledRiseFall> crawledRiseFallList = new ArrayList<>();
        Elements elements = document.select(".dataview-body table tbody tr");

        elements.forEach(element -> {
            Elements row = element.select("td");

            String plateName = row.get(1).text();
            String theRange = row.get(3).text();
            // 去除 %
            theRange = theRange.substring(0, theRange.length() - 2);

            double range = Double.parseDouble(theRange);
            int status = range > 0 ? 2 : 1;
            if (range == 0) {
                status = 3;
            }

            CrawledRiseFall crawledRiseFall = new CrawledRiseFall(plateName, range, status);
            crawledRiseFallList.add(crawledRiseFall);
        });

        return crawledRiseFallList;
    }

    /**
     * 获取对应 Document
     *
     * @param url url
     * @return {@code Document }
     * @author qksuki
     * @date 2024-05-20 07:18:58
     */
    private Document getTargetDocument(String url) {
        HtmlPage page;

        // 1. 配置 WebClient
        try (WebClient webClient = new WebClient()) {
            webClient.getOptions().setJavaScriptEnabled(crawlerProperties.getSetJavaScriptEnabled());
            webClient.getOptions().setCssEnabled(crawlerProperties.getSetCssEnabled());
            webClient.getOptions().setThrowExceptionOnScriptError(crawlerProperties.getSetThrowExceptionOnScriptError());
            webClient.getOptions().setThrowExceptionOnFailingStatusCode(crawlerProperties.getSetThrowExceptionOnFailingStatusCode());

            // 2. 访问目标网页
            page = webClient.getPage(url);

            // 3. 等待JS执行完毕
            webClient.waitForBackgroundJavaScript(crawlerProperties.getJsLoadingTime());
        } catch (IOException e) {
            log.error("访问目标页面失败, {}", e.getMessage());
            throw new BaseException(AppHttpCodeEnum.PAGE_LOAD_FAILURE);
        }

        // 4. 获取解析页面的HTML
        String pageAsXml = page.asXml();
        return Jsoup.parse(pageAsXml);
    }
}

